/*
 * $Id:
 *
 * Copyright (C) 2011 Refiner Inc.
 * All right reserved
 *
 *============================================================
 * +rVPN Stack : AppIF (Application Interface)
 *============================================================
 * abstract: This module provides control I/F to rVPN stack
 * author  : MURATA
 * history :
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <netinet/tcp.h>
#ifdef NO_THREADS
#define USE_THREAD    0
#else
#define USE_THREAD    1
#endif
#include "rrutil.h"
#include "rvs_appif.h"
#include "rvs_socket.h"

#ifdef APPIFLOGPATH
#ifndef APPIFLOGSIZE
#define	APPIFLOGSIZE	(10*1024)
#endif
#ifndef APPIFLOGROT
#define	APPIFLOGROT		2
#endif


char *_rlog_file = APPIFLOGPATH;      // log file name
int _rlog_siz = APPIFLOGSIZE;         // maximum log size (Bytes)
int _rlog_rot = APPIFLOGROT;         // number of rotation

void
rlog_rotate(int g0)
{
    int g;
    char f1[1025], f2[1025];
    struct stat s;

    strcpy(f1, _rlog_file);
    strcat(f1, ".0");
    strcpy(f2, f1);
    for (g=g0;g>=0;g--)
    {
        if (g)
        {
            f1[strlen(f1)-1] = '0'+g;
        }
        else
        {
            f1[strlen(f1)-2] = 0;
        }
        if (g!=g0)
        {
            f2[strlen(f2)-1] = '0'+g+1;
            if (stat(f1, &s) != -1)
            {
                rename(f1, f2);
            }
        }
    }
}

int rapi_open(void)
{
    char *mode;
    struct stat rlog_stat;

    if (_rlog_file && strlen(_rlog_file))
    {
        mode = "a+";
        (void)stat(_rlog_file, &rlog_stat);
        if (rlog_stat.st_size > _rlog_siz)
        {
            if (_rlog_rot)
            {
                rlog_rotate(_rlog_rot);
                mode = "w+";
            }
        }
        if (freopen(_rlog_file, mode, stderr) == NULL)
        {
            return(-1);
        }
        (void)stat(_rlog_file, &rlog_stat);
    }
    return(0);
}

#endif /* APPIFLOGPATH */

rvs_conf_t rapi_conf = {
    /* rvsaddr_t staddr; */
    {{0}},
    0, /* addrlen */
    0, /* init */
    0 /* exit */
};


/* 初期化中フラグ */
static int rapi_initializing = 0;

struct sock_tab {
    int     sock;
    int     used;
} rapi_sock[RVS_MAX_IFSOCK];


#ifdef SOCK_SPLIT
rvs_socks_t socks[MAX_FDS];
#endif /* SOCK_SPLIT */

#ifndef NO_THREADS
static void* _sock_mutex = NULL;
#endif

inline int rapi_sock_lock(void)
{
#ifdef NO_THREADS
    return 0;
#else
    if (_sock_mutex != NULL) {
        return rr_mutex_lock(_sock_mutex);
    }
    return 0;
#endif
}

inline void rapi_sock_unlock(void)
{
#ifndef NO_THREADS
    if (_sock_mutex != NULL) {
        rr_mutex_unlock(_sock_mutex);
    }
#endif
}

static int connect_stack(void)
{
    int ns, sock;
    int on = 1;

    /* ソケットの生成 */
    if (rapi_conf.staddr.sa.sa_family == AF_INET) {
        sock = socket (PF_INET, SOCK_STREAM, 0);
    } else {
        sock = socket (PF_UNIX, SOCK_STREAM, 0);
    }
    if (sock < 0) {
        rvs_set_errno(errno);
        LOGE("%s: socket: %s\n", __FUNCTION__, strerror(errno));
        return -1;
    }

    /* NODELAY属性の設定 */
    if (rapi_conf.staddr.sa.sa_family == AF_INET) {
        if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&on, sizeof(on)) < 0) {
            /* TODO: log error message */
            rvs_set_errno(errno);
            close(sock);
            LOGE("%s: setsockopt(TCP_NODELAY): %s\n", __FUNCTION__, strerror(errno));
            return -1;
        }
    }

    /* rVPNスタックへのconnect */
    if (connect(sock, &rapi_conf.staddr.sa, rapi_conf.addrlen) < 0) {
        rvs_set_errno(errno);
        close(sock);
        struct sockaddr_in* sin=0;
        sin = (struct sockaddr_in*)&rapi_conf.staddr.sa;
        LOGE("%s: connect: %s(to %s:%d)\n",
                 __FUNCTION__, strerror(errno),
                inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
        return -1;
    }

    /* 接続していないエントリを探す */
    rapi_sock_lock();
    for (ns=0; ns<RVS_MAX_IFSOCK; ns++) {
        if (!rapi_sock[ns].used && (rapi_sock[ns].sock == INVALID_SOCKET)) {
            rapi_sock[ns].used = 1;
            break;
        }
    }
    /*rapi_sock[ns].sock = sock;*/
    rapi_sock_unlock();

    if (ns == RVS_MAX_IFSOCK) {
        rvs_set_errno(ENOBUFS);
        LOGE("%s: sock num overflow\n", __FUNCTION__);
        close(sock);
        return -1;
    }
    rapi_sock[ns].sock = sock;

    LOGD("%s: connect succeed. ==>%d[%d]\n", __FUNCTION__, ns, sock);

    return ns;
}

int rvs_api_sock(int ns)
{
    if (ns >= 0 && ns < RVS_MAX_IFSOCK && rapi_sock[ns].used) {
        return rapi_sock[ns].sock;
    } else {
        return -1;
    }
}

/*
 * rvs_api_init - App I/Fの初期化
 *
 *     addr : App I/Fの接続先(rVPNスタックの)アドレス
 *
 *   戻り値
 *     正常時は0。エラー発生時は-1。
 *     rvs_errnoにエラー番号を設定する。
 */
int rvs_api_init(struct sockaddr* addr)
{
    int i, ret;

    LOGD("%s: Enter\n", __FUNCTION__);

    if (is_rvsapi_initialized()) return 0;
    if (rapi_initializing) {
        while (rapi_initializing) {
            usleep(10000);
        }
        LOGD("%s: someone initialized.\n", __FUNCTION__);
        return 0;
    }
    rapi_initializing = 1;

    LOGD("%s: Start initializing\n", __FUNCTION__);
#ifdef APPIFLOGPATH
    rapi_open();
#endif /* APPIFLOGPATH */

    /* Mutex生成 */
#ifndef NO_THREADS
    _sock_mutex = rr_mutex_init();
#endif
    /* 接続先の保存 */
    if (addr) {
        if (addr->sa_family == AF_UNIX) {
            rapi_conf.addrlen = sizeof(struct sockaddr_un);
            memcpy (&rapi_conf.staddr.saun, addr, rapi_conf.addrlen);
        } else if (addr->sa_family == AF_INET) {
            rapi_conf.addrlen = sizeof(struct sockaddr_in);
            memcpy (&rapi_conf.staddr.sain, addr, rapi_conf.addrlen);
        } else {
            LOGE("%s: FATAL: connecting address is invalid", __FUNCTION__);
            rvs_set_errno(EINVAL);
            rapi_initializing = 0;
            return -1;
        }
    } else {
#if RVS_AF_UNIX
        rapi_conf.addrlen = sizeof(struct sockaddr_un);
        memset (&rapi_conf.staddr.saun, 0, rapi_conf.addrlen);
        rapi_conf.staddr.saun.sun_family = AF_UNIX;
        strncpy(rapi_conf.staddr.saun.sun_path, RVS_API_SOCK_PATH,
                     sizeof(rapi_conf.staddr.saun.sun_path) - 1);
        LOGD("%s: init AF_UNIX(%s)\n", __FUNCTION__, rapi_conf.staddr.saun.sun_path);
#else
        rapi_conf.addrlen = sizeof(struct sockaddr_in);
        memset(&rapi_conf.staddr, 0, rapi_conf.addrlen);
        rapi_conf.staddr.sain.sin_family = AF_INET;
        rapi_conf.staddr.sain.sin_addr.s_addr = inet_addr("127.0.0.1");
        rapi_conf.staddr.sain.sin_port = htons(RVS_API_SOCK_PORT);
        LOGD("%s: init AF_INET(%s:%d)\n", __FUNCTION__, inet_ntoa(rapi_conf.staddr.sain.sin_addr), ntohs(rapi_conf.staddr.sain.sin_port));
#endif
    }

    rapi_sock_lock();

    /* ソケットテーブルの初期化 */
    for (i=0; i<RVS_MAX_IFSOCK; i++) {
        rapi_sock[i].sock = INVALID_SOCKET;
        rapi_sock[i].used = 0;
    }

#ifdef SOCK_SPLIT
    LOGD("%s: initialize socks[%d]\n", __FUNCTION__, MAX_FDS);
    for(i=0; i < MAX_FDS; i++){
      socks[i].rvs_sock = -1;
      socks[i].sock_sts = SOCK_NONE;
    }
#endif

    rapi_sock_unlock();

    rapi_conf.init = 1;
    rapi_conf.exit = 0;

    ret = connect_stack();
    if (ret >= 0) {
        rvs_api_free(ret);
        ret = 0;
    }
    rapi_initializing = 0;

    LOGD("%s: Succeed initializing\n", __FUNCTION__);

    return ret;
}

/*
 * rvs_api_end - App I/Fの終了
 *
 *   戻り値
 *     なし
 */
void rvs_api_end(void)
{
    int i;

    if (!is_rvsapi_initialized()) return;

    rapi_sock_lock();

    rapi_conf.init = 0;
    rapi_conf.exit = 1;

#ifdef SOCK_SPLIT
    LOGD("%s: Enter\n", __FUNCTION__);
    /* release rvs socket and kernel socket */
    for( i=0; i < MAX_FDS; i++){
      if(socks[i].sock_sts != SOCK_NONE){
       	if(socks[i].rvs_sock != -1){
	  /*	  rvs_close(socks[i].rvs_sock);  */
	  socks[i].rvs_sock = -1;
	}
        close(i);
        socks[i].sock_sts = SOCK_NONE;
      }
    }
#endif  /* SOCK_SPLIT */

    /* ソケットをすべてclose */
    for (i=0; i<RVS_MAX_IFSOCK; i++) {
        if (rapi_sock[i].sock != INVALID_SOCKET) {
            close(rapi_sock[i].sock);
            rapi_sock[i].sock = INVALID_SOCKET;
            rapi_sock[i].used = 0;
        }
    }

    rapi_sock_unlock();

    /* Mutex削除 */
#ifndef NO_THREADS
    rr_mutex_destroy(_sock_mutex);
#endif
}

/*
 * rvs_api_new - フリーのソケットを割り当てる
 *
 *   戻り値
 *     正常時はrapi_sock番号。エラー発生時は-1。
 *     rvs_errnoにエラー番号を設定する。
 */
int rvs_api_new(void)
{
    int ns;

    if (!is_rvsapi_initialized()) {
        if (is_rvsapi_exited() || (rvs_api_init(NULL) < 0)) {
            LOGE("%s: exiting or initialization error\n", __FUNCTION__);
            return -1;
        }
    }

    rapi_sock_lock();

    /* 接続済みでかつ未使用のソケットを探す */
    for (ns=0; ns<RVS_MAX_IFSOCK; ns++) {
        if (!rapi_sock[ns].used && rapi_sock[ns].sock != INVALID_SOCKET) {
            rapi_sock[ns].used = 1;
            break;
        }
    }

    rapi_sock_unlock();

    /* 見つかった？ */
    if (ns < RVS_MAX_IFSOCK) {
        struct sockaddr_storage ss;
        socklen_t sslen;
        //LOGD("%s: api_sock = %d[%d]\n", __FUNCTION__, ns, rapi_sock[ns].sock);
        if (getpeername(rapi_sock[ns].sock, (struct sockaddr*)&ss, &sslen)) {
            LOGD("%s: socket(%d) is broken. Try to reconnect...\n",
                     __FUNCTION__, rapi_sock[ns].sock);
            rvs_api_remove(ns);
        } else {
            return ns;
        }
    }

    /* 新しく接続する */
    return connect_stack();
}


/*
 * rvs_api_free - ソケットを解放する
 *
 *     ns : rapi_sock番号
 *
 *   戻り値
 *     なし
 */
void rvs_api_free(int ns)
{
    if (ns < 0 || ns >= RVS_MAX_IFSOCK || !rapi_sock[ns].used) {
        rvs_set_errno(EINVAL);
        return;
    }

    rapi_sock[ns].used = 0;
}

/*
 * rvs_api_remove - ソケットをクローズする
 *
 *     ns : rapi_sock番号
 *
 *   戻り値
 *     なし
 */
void rvs_api_remove(int ns)
{
    if (ns < 0 || ns >= RVS_MAX_IFSOCK || !rapi_sock[ns].used) {
        rvs_set_errno(EINVAL);
        return;
    }
    LOGD("%s: close(%d)\n", __FUNCTION__, rapi_sock[ns].sock);
    close(rapi_sock[ns].sock);
    rapi_sock[ns].sock = INVALID_SOCKET;
    rapi_sock[ns].used = 0;
}


